// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <array>

#include <ktest/ktest.h>

#include <deneb/lru.h>

using namespace deneb;

struct CallbackTest : public ::testing::Test {
  Cache<int, int> cache;
};

TEST_F(CallbackTest, HitCallbacksGetCalled) {
  std::array<int, 3> counts = {0, 0, 0};

  cache.hit_callback([&counts](auto& key, auto& value) { counts[key] += 1; });

  cache.emplace(0, 0);
  cache.emplace(1, 1);
  cache.emplace(2, 2);

  ASSERT_TRUE(cache.contains(0));
  EXPECT_EQ(counts[0], 1);
  EXPECT_EQ(counts[1], 0);
  EXPECT_EQ(counts[2], 0);

  cache.find(2);
  EXPECT_EQ(counts[0], 1);
  EXPECT_EQ(counts[1], 0);
  EXPECT_EQ(counts[2], 1);

  cache.lookup(1);
  EXPECT_EQ(counts[0], 1);
  EXPECT_EQ(counts[1], 1);
  EXPECT_EQ(counts[2], 1);

  cache.lookup(0);
  EXPECT_EQ(counts[0], 2);
  EXPECT_EQ(counts[1], 1);
  EXPECT_EQ(counts[2], 1);

  cache.contains(5);
  EXPECT_EQ(counts[0], 2);
  EXPECT_EQ(counts[1], 1);
  EXPECT_EQ(counts[2], 1);
}

TEST_F(CallbackTest, MissCallbacksGetCalled) {
  std::array<int, 3> counts = {0, 0, 0};

  cache.miss_callback([&counts](auto& key) { counts[key] += 1; });

  cache.emplace(0, 0);

  ASSERT_TRUE(cache.contains(0));
  EXPECT_EQ(counts[0], 0);
  EXPECT_EQ(counts[1], 0);
  EXPECT_EQ(counts[2], 0);

  cache.find(2);
  EXPECT_EQ(counts[0], 0);
  EXPECT_EQ(counts[1], 0);
  EXPECT_EQ(counts[2], 1);

  cache.find(1);
  EXPECT_EQ(counts[0], 0);
  EXPECT_EQ(counts[1], 1);
  EXPECT_EQ(counts[2], 1);

  cache.contains(1);
  EXPECT_EQ(counts[0], 0);
  EXPECT_EQ(counts[1], 2);
  EXPECT_EQ(counts[2], 1);
}

TEST_F(CallbackTest, AccessCallbacksGetCalled) {
  std::array<int, 3> counts = {0, 0, 0};

  cache.access_callback(
      [&counts](auto& key, bool found) { counts[key] += found ? 1 : -1; });

  cache.emplace(0, 0);

  ASSERT_TRUE(cache.contains(0));
  EXPECT_EQ(counts[0], 1);
  EXPECT_EQ(counts[1], 0);
  EXPECT_EQ(counts[2], 0);

  cache.find(2);
  EXPECT_EQ(counts[0], 1);
  EXPECT_EQ(counts[1], 0);
  EXPECT_EQ(counts[2], -1);

  cache.find(1);
  EXPECT_EQ(counts[0], 1);
  EXPECT_EQ(counts[1], -1);
  EXPECT_EQ(counts[2], -1);

  cache.contains(1);
  EXPECT_EQ(counts[0], 1);
  EXPECT_EQ(counts[1], -2);
  EXPECT_EQ(counts[2], -1);

  cache.find(0);
  EXPECT_EQ(counts[0], 2);
  EXPECT_EQ(counts[1], -2);
  EXPECT_EQ(counts[2], -1);
}

TEST_F(CallbackTest, CallbacksAreNotCalledAfterBeingCleared) {
  int hit = 0, miss = 0, access = 0;
  cache.hit_callback([&hit](auto&, auto&) { hit += 1; });
  cache.miss_callback([&miss](auto&) { miss += 1; });
  cache.access_callback([&access](auto&, bool) { access += 1; });

  cache.emplace(0, 0);

  cache.contains(0);
  cache.find(1);

  ASSERT_EQ(hit, 1);
  ASSERT_EQ(miss, 1);
  ASSERT_EQ(access, 2);

  cache.clear_all_callbacks();

  cache.contains(0);
  cache.find(1);
  cache.find(2);

  ASSERT_EQ(hit, 1);
  ASSERT_EQ(miss, 1);
  ASSERT_EQ(access, 2);
}
